马踏棋盘(马的遍历问题)

问题描述:

在8×8方格(国际象棋)的棋盘上,从任意指定方格出发,为马寻找一条走遍棋盘每一格并且只经过一次的一条路径。

问题分析:

首先这是一个搜索问题,运用深度优先搜索进行求解是完全可行的,它输入的是全部解,但是马遍历当8×8时解是非常之多的,用天文数字形容也不为过,这样一来求解的过程就非常慢,并且出一个解也非常慢。
怎么才能快速地得到部分解呢?


其实马踏棋盘的问题很早就有人提出,且早在1823年,J.C.Warnsdorff就提出了一个有名的算法。在每个结点对其子结点进行选取时,优先选择‘出口’最小的进行搜索,‘出口’的意思是在这些子结点中它们的可行子结点的个数,也就是‘孙子’结点越少的越优先跳,为什么要这样选取,这是一种局部调整最优的做法,如果优先选择出口多的子结点,那出口少的子结点就会越来越多,很可能出现‘死’结点(顾名思义就是没有出口又没有跳过的结点),这样对下面的搜索纯粹是徒劳,这样会浪费很多无用的时间,反过来如果每次都优先选择出口少的结点跳,那出口少的结点就会越来越少,这样跳成功的机会就更大一些。这种算法称为为贪心算法,也叫贪婪算法或启发式算法,它对整个求解过程的局部做最优调整,它只适用于求较优解或者部分解,而不能求最优解。这样的调整方法叫贪心策略,至于什么问题需要什么样的贪心策略是不确定的,具体问题具体分析。实验可以证明马遍历问题在运用到了上面的贪心策略之后求解速率有非常明显的提高,如果只要求出一个解甚至不用回溯就可以完成,因为在这个算法提出的时候世界上还没有计算机,这种方法完全可以用手工求出解来,其效率可想而知。

算法思想步骤概括为下面的几点:
①找到一个位置的各个方向的出口(OUT1)。
②对各个方向的出口进行二次出口(OUT2)的搜索。
③记录每个方向上出口(OUT1)的二次出口(OUT2),(OUT2)最小的的那个(OUT1)即为接下来要访问的位置。
④将每次访问过的位置放入容器。


使用贪心算法来进行马踏棋盘(8*8国际象棋)的实现如下(C++):

#include <cstdio>  
#include <cstring>  
#include <vector>  
#include <iostream>  
using namespace std;  

class Pos                        //定义棋盘上格子的坐标位置
{  
public:  
    int x;  
    int y;  
    Pos(int x, int y):x(x),y(y){}  
};  

vector<Pos> store;       //用于存储路径  

//棋盘数据:  
const int WIDTH = 8;       //棋盘宽和高  (国际象棋)
const int HEIGHT = 8;  
int board[WIDTH+1][HEIGHT+1];  //棋盘数组保存数据为每个位置对应马的路线的第几步  

//dir为马的八个方向  
const int dir[8][2] = {{-2,-1},{-2,1},{-1,2},{1,2},{2,1},{2,-1},{1,-2},{-1,-2}};  

//求(i,j)位置的出口,并返回所有出口和对应的出口个数  
int exitn(int i,int j,int s,int a[])  
{    
    int k,i1,j1;  
    int count;  //计数出口的个数  
    for (count=k=0; k<8; k++)  
    {
        i1 = i + dir[(s+k)%8][0];  //八方向横坐标和纵坐标,(s+k)%8让索引保持在八方向上  
        j1 = j + dir[(s+k)%8][1];  
        if(i1>=0 && i1<HEIGHT && j1>=0 && j1<WIDTH && board[i1][j1]==0) //在棋盘的范围内并且没有走过  
            a[count++]=(s+k)%8;  
    }  
    return count;  
}  
//将(i,j)节点以s开始的下一个节点序列中,节点数最小的一个返回  //next(istartX, istartY, start)
int next(int i,int j,int s)  
{  
    int m,k,go,min,a[8],b[8],temp;  
    m = exitn(i, j, s, a);  //当前位置有出口数  
    if(m==0)                //没有  
        return -1;  
    for(min=8,k=0; k<m; k++)       //搜寻出口最少的位置  
    {  
        temp = exitn(i+dir[a[k]][0], j+dir[a[k]][1], s, b);  
        if(temp < min)             //保存最小出口  
        {  
            min = temp;  
            go = a[k];             //保存方向索引  
        }  
    }  
    return go;             //返回最少出口位置的方向索引  
}  

int main(int argc, char* argv[])  
{  
    cout << "    ----------------国际象棋中马的遍历----------------- \n"<< endl;  
    int istartX(0), istartY(0);     //表示当前起始位置  
    while (1)  
    {  
        cout << "请输入马的起始位置行号(0~7):";
        cin >> istartX;  
        if (istartX >= 0 && istartX <=7) break;
    }  

    while (1)  
    {  
        cout << "请输入马的起始位置列号(0~7):";  
        cin >> istartY;  
        if (istartY >= 0 && istartY <= 7) break;  
    }  

    int step, flag, start=1;    //step表示第几步,flag标记下一步的方向,start表示方向索引的初始值  
    memset(board,0,sizeof(int)*WIDTH*HEIGHT);  //初始化棋盘,0表示没有走过//*********************************************************************
    board[istartX][istartY]=1;                 //起始位置的第一步,1表示第一步,N表示第N步  
    Pos v(istartX, istartY);  
    store.push_back(v);  

    cout << "\n棋盘中的轨迹:(数字表示第几步,所在位置为棋盘位置)" << endl;  
    for(step=2; step <= WIDTH*HEIGHT; step++)  //从第二步开始,直到走满整个棋盘  
    {  
        if ((flag = next(istartX, istartY, start)) == -1)      //返回-1,没有找到出口  
            break;  
        istartX += dir[flag][0];                 //下一步的起始坐标  
        istartY += dir[flag][1];  
        board[istartX][istartY] = step;                    //保存当前步到棋盘作为标记  
        Pos v(istartX,istartY);  
        store.push_back(v);                     //将位置坐标存储  
    }  
    int i, j;  
    for(i=0; i<HEIGHT; i++)                    //输出棋盘保存的路径  
    {  
        for(j=0; j<WIDTH; j++)  
            printf("%5d", board[i][j]);        //5格对齐  
        cout << endl;  
    }  

    int count = 0;  
    cout << "马遍历的路径为:" << endl;  
    for(vector<Pos>::iterator it = store.begin(); it != store.end(); ++it)  
    {  
        cout << "第" << ++count << "步: (" << (*it).x << "," << (*it).y << ")" << endl;  
    }  
  system("pause");
    return 0;  
}
  • 13
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
C编程棋盘问题,是一个经典的递归问题。具体问题描述如下: 给定一个 $n\times n$ 的棋盘和一个初始位置 $(x,y)$,要求用走日的方式,从该位置开始遍整个棋盘,每个格子只能经过一次。输出遍的路径。 棋盘上的移动规则如下:假设当前所在的位置为 $(i,j)$,那么可以向下面的 8 个方向中的任意一个方向前进 2 格,然后向左或向右走 1 格。注意,不能走出棋盘。 下面是一份 C语言代码,实现了以上问题的解决方案: ```c #include <stdio.h> #include <stdlib.h> #define SIZE 8 int chessboard[SIZE][SIZE] = {0}; // 初始化棋盘 int dx[8] = {1, 2, 2, 1, -1, -2, -2, -1}; int dy[8] = {2, 1, -1, -2, -2, -1, 1, 2}; void PrintChessBoard() { for (int i = 0; i < SIZE; i++) { for (int j = 0; j < SIZE; j++) { printf("%-3d", chessboard[i][j]); } printf("\n"); } } // 判断该点是否合法 int IsSafe(int x, int y) { return (x >= 0 && x < SIZE && y >= 0 && y < SIZE && chessboard[x][y] == 0); } int DFS(int x, int y, int step) { int i, next_x, next_y; if (step == SIZE * SIZE) { // 遍完整个棋盘 PrintChessBoard(); // 输出遍路径 return 1; } for (i = 0; i < 8; i++) { // 枚举8个可能的方向 next_x = x + dx[i]; next_y = y + dy[i]; if (IsSafe(next_x, next_y)) { chessboard[next_x][next_y] = step + 1; // 标记已经走过的位置 if (DFS(next_x, next_y, step + 1)) { // 递归下一步 return 1; } chessboard[next_x][next_y] = 0; // 回溯 } } return 0; } int main() { int x, y; printf("请输入初始位置:\n"); scanf("%d %d", &x, &y); chessboard[x][y] = 1; // 标记起点 if (!DFS(x, y, 1)) { // 如果没有找到解 printf("未找到解!\n"); } return 0; } ``` 运行该程序,输入起始位置,即可输出遍路径。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值